import java.awt.*;
import java.awt.event.*;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

public class CamServer implements Runnable{
	
	ServerSocket ss;
	Socket s;
	DataInputStream dis;
	BufferedOutputStream bos = null;
	Runtime rt = Runtime.getRuntime();
	private int port;
	private String command;
	private String modprobe_command;
	private String pipe;
	boolean exiting = false;
	final PropPanel pp = new PropPanel();
	private boolean v4lRunning = false;
	private static final int YUV420SP_FRAME_LENGTH = 38016;
	byte[] data = new byte[YUV420SP_FRAME_LENGTH];
	byte[] rgbdata = new byte[176*144*3];

	
	public static void main(String[] args) throws Exception{
		CamServer cs = new CamServer();
		//start Tray
		cs.startTray();
		//New thread for v4l feeder and kernel module loading 
		(new Thread(cs)).start();
		//init
		cs.init();
		//wait for client -> transfer
		while (!cs.exiting){
				cs.waitForClient();
				if (cs.s!=null && !cs.s.isClosed())
					cs.transfer();
		}
		//Exiting...
		cs.disconnect();
		synchronized(cs){
			cs.notify();
		}
		
	}
	
	void init() {
		try {
			port = pp.getPort();
			command = pp.getCommand();
			modprobe_command = pp.getModprobeCommand();
			pipe = pp.getPipe();
			ss = new ServerSocket(port);
			ss.setSoTimeout(1000);
			while (!v4lRunning){try {
				Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();
			}}
			bos = new BufferedOutputStream(new FileOutputStream(pipe));
		} catch (UnknownHostException e) {e.printStackTrace();
		} catch (FileNotFoundException e) {e.printStackTrace();
		} catch (IOException e) {e.printStackTrace();}
	}
	
	void waitForClient(){
		System.out.println("waiting for client...");
		try {s = ss.accept();} catch (IOException e) {}	
	}
	
	void transfer(){
		System.out.println("Transfer started...");
		int i=0;
		int pos=0;
		try {
			dis = new DataInputStream(s.getInputStream());
			while ((i=dis.read(data,pos,data.length-pos)) != -1){
				if (!exiting){
				pos+=i;
				if (pos == data.length){
					toRGB888(data,176,144,rgbdata);
					bos.write(rgbdata,0,rgbdata.length);
					pos=0;
				}
				}
			}
		} catch (IOException e) {e.printStackTrace();}
		System.out.println("Transfer finished");
		disconnect();
	}
	
	void disconnect(){
		System.out.println("Disconnecting...");
		try {
			if (dis!=null) dis.close();
			if (s!=null) s.close();
			if (ss!=null && exiting) ss.close();
			if (bos!=null && exiting){ bos.flush(); bos.close();}
		} catch (IOException e) {e.printStackTrace();}
		System.out.println("Disconnected");
	}
	
	void exit(){
		System.out.println("Exiting...");
		exiting = true;
		try { rt.exec("pkill v4l2vdSource"); } catch (IOException e) {e.printStackTrace();}
		synchronized(this){
			System.out.println("Exiting... waiting");
			try { wait();} catch (InterruptedException e) {e.printStackTrace();}
		}
		System.exit(0);
	}
	
	public void run(){
			String line;
			BufferedReader br=null;
			Process p=null;
			
		try {
			//load v4l2vd module
			p = rt.exec(modprobe_command);
			System.out.println(modprobe_command);
			//print error
			br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
			while ( (line = br.readLine()) != null)
                System.out.println(line);		
			//run v4l2vdSource
			p = rt.exec(command);
			v4lRunning  = true;
			//print output
			br = new BufferedReader(new InputStreamReader(p.getInputStream()));
			while ( (line = br.readLine()) != null)
                System.out.println(line);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
	private void startTray() {
		final TrayIcon trayIcon;

		if (SystemTray.isSupported()) {

		    SystemTray tray = SystemTray.getSystemTray();
		    Image image = Toolkit.getDefaultToolkit().getImage("src/icon.png");

		    ActionListener exitListener = new ActionListener() {
		        public void actionPerformed(ActionEvent e) {
		            exit();
		        }
		    };
		            
		    ActionListener propListener = new ActionListener() {
		        public void actionPerformed(ActionEvent e) {
		        	pp.pack();
		        	pp.setVisible(true);

		        }
		    };
		    
		    PopupMenu popup = new PopupMenu();
		    MenuItem exitItem = new MenuItem("Exit");
		    MenuItem propItem = new MenuItem("Properties");
		    exitItem.addActionListener(exitListener);
		    propItem.addActionListener(propListener);
		    popup.add(exitItem);
		    popup.add(propItem);

		    trayIcon = new TrayIcon(image, "Tray Demo", popup);
		            
		    trayIcon.setImageAutoSize(true);

		    try {
		        tray.add(trayIcon);
		    } catch (AWTException e) {
		        System.err.println("TrayIcon could not be added.");
		    }

		} else {

		    //  System Tray is not supported

		}

	}
	
    private void toRGB888(byte[] yuvs, int width, int height, byte[] rgbs) {
        //the end of the luminance data
        final int lumEnd = width * height;
        //points to the next luminance value pair
        int lumPtr = 0;
        //points to the next chromiance value pair
        int chrPtr = lumEnd;
        //points to the next byte output pair of RGB565 value
        int outPtr = 0;
        //the end of the current luminance scanline
        int lineEnd = width;

        while (true) {

            //skip back to the start of the chromiance values when necessary
            if (lumPtr == lineEnd) {
                if (lumPtr == lumEnd) break; //we've reached the end
                //division here is a bit expensive, but's only done once per scanline
                chrPtr = lumEnd + ((lumPtr  >> 1) / width) * width;
                lineEnd += width;
            }

            //read the luminance and chromiance values
            final int Y1 = yuvs[lumPtr++] & 0xff;
            final int Y2 = yuvs[lumPtr++] & 0xff;
            final int V = (yuvs[chrPtr++] & 0xff);
            final int U = (yuvs[chrPtr++] & 0xff);
            int R, G, B;
            int C1, C2, D, E;
            
            C1 = Y1 - 16;
            C2 = Y2 - 16;
            D = U - 128;
            E = V - 128;

            //generate first RGB components
            R = (298 * C1 + 409 * E + 128) >> 8; 
            if(R < 0) R = 0; else if(R > 255) R = 255; 
            G = (298 * C1 - 100 * D - 208 * E + 128) >> 8; 
            if(G < 0) G = 0; else if(G > 255) G = 255; 
            B = (298 * C1 + 516 * D + 128) >> 8;
            if(B < 0) B = 0; else if(B > 255) B = 255;

            rgbs[outPtr++]  = (byte) (R&0xff);
            rgbs[outPtr++]  = (byte) (G&0xff);
            rgbs[outPtr++]  = (byte) (B&0xff);
                    
            //generate second RGB components
            R = (298 * C2 + 409 * E + 128) >> 8; 
            if(R < 0) R = 0; else if(R > 255) R = 255; 
            G = (298 * C2 - 100 * D - 208 * E + 128) >> 8; 
            if(G < 0) G = 0; else if(G > 255) G = 255; 
            B = (298 * C2 + 516 * D + 128) >> 8;
            if(B < 0) B = 0; else if(B > 255) B = 255;

            rgbs[outPtr++]  = (byte) (R&0xff);
            rgbs[outPtr++]  = (byte) (G&0xff);
            rgbs[outPtr++]  = (byte) (B&0xff);

        }
    }

}
